package negocio;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import srv.*;
import beans.*;
import exceptions.CajaException;
import exceptions.FacturaException;
import exceptions.MesaException;
import exceptions.MozoException;
import exceptions.PedidoException;
import exceptions.PlatoException;
import exceptions.ProductoException;
import negocio.Barra;
import negocio.Cafeteria;
import negocio.Caja;
import negocio.Carta;
import negocio.Cocina;
import negocio.ComisionMozo;
import negocio.Deposito;
import negocio.Ingrediente;
import negocio.Insumo;
import negocio.Mesa;
import negocio.Mozo;
import negocio.Pedido;
import negocio.Plato;
import negocio.Producto;
import negocio.Receta;
import negocio.Salon;
import negocio.SemiElaborado;

@Entity
@Table(name="restaurantes")
public class Restaurante implements Serializable
{	

	private static final long serialVersionUID = 1L;
	
	@Id @GeneratedValue(strategy = GenerationType.AUTO)
	private int idRastaurante;
	@Column(nullable=false, length=50)
	private String nombre;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_cocina", referencedColumnName="id_cocina")
	private Cocina cocina;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_caja", referencedColumnName="id_caja")
	private Caja caja;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_salon", referencedColumnName="id_salon")
	private Salon salon;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_barra", referencedColumnName="id_barra")
	private Barra barra;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_cafeteria", referencedColumnName="id_cafeteria")
	private Cafeteria cafeteria;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_deposito", referencedColumnName="id_deposito")
	private Deposito depositoLocal;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="id_carta", referencedColumnName="idCarta")
	private Carta carta;
	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name="idRastaurante", referencedColumnName="idRastaurante", nullable=false)
	private Set<Producto> productos;
	@OneToOne(cascade=CascadeType.ALL)
	@JoinColumn(name="idRastaurante", referencedColumnName="idRastaurante")
	private InformeReposicion informeReposicion;
	
	@Transient
	private PlatoSRV platoSRV;
	@Transient
	private ProductoSRV productoSRV;
	@Transient
	private PedidoSRV pedidosSRV;
	@Transient
	private SectorSRV sectorSRV;
	@Transient
	private MozoSRV mozoSRV;
	@Transient
	private FacturaSRV facturasrv;
	@Transient
	private DepositoSRV depositoSRV;
	@Transient
	private CajaSRV cajaSRV;
	@Transient
	private RestauranteSRV restaurateSRV;
	
	public Restaurante(){}
	
	public Restaurante(String nombre)
	{
		this.nombre = nombre;
		this.depositoLocal = new Deposito("Local");
		this.cocina = new Cocina(depositoLocal);
		this.barra = new Barra(depositoLocal);
		this.cafeteria = new Cafeteria(depositoLocal);
		this.carta = new Carta();
		carta.setVigencia(new Date());//TODO: PONER BIEN ESTO DESP
		this.salon = new Salon();
		this.caja = new Caja();
		caja.setSalon(salon);
		this.productos = new HashSet<Producto>();
		this.informeReposicion = new InformeReposicion();
	}
	
	public void inicializarRestaurante()
	{
		platoSRV = new PlatoSRV();
		productoSRV = new ProductoSRV();
		pedidosSRV = new PedidoSRV();
		sectorSRV = new SectorSRV();
		facturasrv = new FacturaSRV();
		depositoSRV = new DepositoSRV();
		restaurateSRV = new RestauranteSRV();
		cajaSRV = new CajaSRV();
		mozoSRV = new MozoSRV();
	}
	
	public int getIdRastaurante() {
		return idRastaurante;
	}

	public void setIdRastaurante(int idRastaurante) {
		this.idRastaurante = idRastaurante;
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public Carta getCarta() {
		return carta;
	}

	public void setCocina(Cocina cocina) {
		this.cocina = cocina;
	}

	public void setCaja(Caja caja) {
		this.caja = caja;
	}

	public void setBarra(Barra barra) {
		this.barra = barra;
	}

	public void setCafeteria(Cafeteria cafeteria) {
		this.cafeteria = cafeteria;
	}

	public void setDepositoLocal(Deposito depositoLocal) {
		this.depositoLocal = depositoLocal;
	}
	
	public void setCarta(Carta carta) {
		this.carta = carta;
	}
	
	public Salon getSalon() {
		return salon;
	}

	public void setSalon(Salon salon) {
		this.salon = salon;
	}

	public Cocina getCocina() {
		return cocina;
	}

	public Caja getCaja() {
		return caja;
	}

	public Barra getBarra() {
		return barra;
	}

	public Cafeteria getCafeteria() {
		return cafeteria;
	}

	public Deposito getDepositoLocal() {
		return depositoLocal;
	}

	public Set<Producto> getProductos() {
		return productos;
	}

	public void setProductos(Set<Producto> productos) {
		this.productos = productos;
	}

	/**
	 * Carga del unico salon con su capacidad de cubiertos
	 * @param capacidadMaxCubiertos
	 */
	public void cargarSalon(int capacidadMaxCubiertos)
	{
		restaurateSRV.setCapacidadMaxSalon(this, capacidadMaxCubiertos);
	}
	
	/**
	 * Carga de sectores. Cada uno tiene sus mesas.
	 * @param sectores	ArrayList<SectorVO>
	 */
	public void cargarSectores(ArrayList<SectorVO> sectoresVO)
	{
		ArrayList<Sector> sectores = new ArrayList<>();
		for(SectorVO sectorVO : sectoresVO){
			Sector sector = new Sector();
			sector.buildFromVO(sectorVO);
			sectores.add(sector);
		}
		restaurateSRV.setSectores(this, sectores);
	}

	public void controlarPlanProduccion()
	{
		//**TODO**//
	}

	public void generarInformeReposicion()
	{
		//**TODO**//
	}
	
	public ArrayList<SolicitudReposicion> getSolicitudesReposicion()
	{
		ArrayList<SolicitudReposicion> solicitudes = new ArrayList<>();
		solicitudes.add(depositoLocal.getSolicitudReposicion());
		solicitudes.add(cocina.getDepositoArea().getSolicitudReposicion());
		solicitudes.add(barra.getDepositoArea().getSolicitudReposicion());
		solicitudes.add(cafeteria.getDepositoArea().getSolicitudReposicion());
		return solicitudes;
	}
	
	public SolicitudReposicion obtenerSolicitudReposicionSector(String sector)
	{
		Deposito deposito = obtenerDeposito(sector);
		return deposito.getSolicitudReposicion();
	}
	
	public void agregarMozo(int dni, String nombre, 
			String nombreSector, float porcentajeComision) 
					throws CajaException
	{
		Sector sector = sectorSRV.buscarSector(nombreSector);
		Mozo m = new Mozo();
		m.setId_dni(dni);
		m.setNombre(nombre);
		m.setSector(sector);
		m.setPorcentaje_Comision(porcentajeComision);
		caja.agregarMozo(m);
		cajaSRV.updateCaja(caja);
	}
	
	public Producto agregarInsumo(ProductoVO productoVO) 
			throws ProductoException
	{
		Insumo insumo = new Insumo();
		insumo.buildFromVO(productoVO);
		//Regla de negocio: los insumos no tienen precio de venta.
		insumo.setPrecioVenta(0);
		
		if(!productos.contains(insumo));
			productos.add(insumo);
		restaurateSRV.updateRestaurante(this);
		return insumo;
	}
	
	public Producto agregarSemielaborado(ProductoVO productoVO, RecetaVO recetaVO) 
			throws ProductoException
	{
		SemiElaborado semiElaborado = new SemiElaborado();
		semiElaborado.buildFromVO(productoVO);
		Receta receta = new Receta();
		receta.buildFromVO(recetaVO);
		semiElaborado.setReceta(validarRecetaSemielaborado(receta));
		
		if(!productos.contains(semiElaborado));
			productos.add(semiElaborado);
		restaurateSRV.updateRestaurante(this);		
		return semiElaborado;
	}
	
	private void agregarStockProducto(Producto producto,
			float cantidad, String responsable, Date fechaCompra, 
			Date fechaVencimiento, int nroLote, String sector)
	{
		Deposito d = obtenerDeposito(sector);
		depositoSRV.realizarMovimiento(d, producto, 
				cantidad, responsable, fechaCompra, 
				fechaVencimiento, nroLote);
	}
	
	public void reponerInsumos(ArrayList<ListaReposicion> listasReposicion)
	{
		for(ListaReposicion s : listasReposicion){
			for(ItemListaReposicion i : s.getItems()){
				agregarStockProducto(i.getProducto(), i.getCantidad(), 
						i.getResponsable(), i.getFechaCompra(), 
						i.getFechaVencimiento(), i.getNroLote(),
						i.getSector());
			}
		}
		verificarStocksItemsSolicitud();
	}
	
	private void verificarStocksItemsSolicitud()
	{
		depositoSRV.verificarStocksItemsSolicitud(depositoLocal);
		depositoSRV.verificarStocksItemsSolicitud(cocina.getDepositoArea());
		depositoSRV.verificarStocksItemsSolicitud(barra.getDepositoArea());
		depositoSRV.verificarStocksItemsSolicitud(cafeteria.getDepositoArea());
	}
	
	public void agregarProductoEstimado(Producto producto, 
			String nombreDeposito, float estimadoDiario, 
			int stockReposicion) 
			throws ProductoException
	{
		try 
		{
			ProductoEstimado p = new ProductoEstimado();
			p.setProducto(producto);
			p.setEstimadoDiario(estimadoDiario);
			p.setStockReposicion(stockReposicion);
			
			Deposito deposito = obtenerDeposito(nombreDeposito);
			
			depositoSRV.agregarProductoEstimado(deposito, p);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
			throw new ProductoException("Error al grabar el producto.");
		}
	}
	
	public Producto buscarProducto(String nombre)
	{
		for(Producto producto : productos){
			if(producto.getNombre().equals(nombre))
				return producto;
		}
		return null;
	}
	
	private Deposito obtenerDeposito(String nombreDeposito) {
		Deposito deposito = null;
		
		switch (nombreDeposito) {
			case "Local": deposito = depositoLocal;	break;
			case "Cocina": deposito = cocina.getDepositoArea();	break;
			case "Barra": deposito = barra.getDepositoArea();	break;
			case "Cafeteria": deposito = cafeteria.getDepositoArea();	break;
			default: break;
		}
		
		return deposito;
	}

	private Receta validarRecetaSemielaborado(Receta receta)
	{
		for(Ingrediente i : receta.getIngredientes()){
			Producto p = i.getProducto();
			i.setProducto(productoSRV.getProducto(p.getNombre()));
		}
		return receta;
	}
	
	public void agregarPlato(PlatoVO platoVO) throws PlatoException
	{
		Plato plato = buildPlatoFromVO(platoVO);
		if(carta.getPlatos().contains(plato))
		{
			throw new PlatoException("El plato ya existe.");
		}
		else
		{
			try 
			{
				carta.getPlatos().add(plato);
				restaurateSRV.updateCarta(carta);
			}
			catch (Exception e) 
			{
				throw new PlatoException("Error al grabar el plato.");
			}			
		}
	}
	
	private Plato buildPlatoFromVO(PlatoVO platoVO)
	{
		try {
			Plato plato = new Plato();
			plato.setArea(obtenerArea(platoVO.getArea()));
			plato.setComision_extra(platoVO.getComision_extra());
			plato.setNombre(platoVO.getNombre());
			plato.setPrecio(platoVO.getPrecio());
			plato.setTextRubro(platoVO.getRubro());
			ArrayList<Ingrediente> ingredientes = new ArrayList<>();
			for(IngredienteVO i : platoVO.getReceta().getIngredientes()){
				Ingrediente ingrediente = new Ingrediente();
				ingrediente.setProducto(buscarProducto(i.getProducto().getNombre()));
				ingrediente.setCantidad(i.getCantidad());
				ingrediente.setUnidad(i.getUnidad());
				ingredientes.add(ingrediente);
			}
			plato.setReceta(new Receta(ingredientes));
			return plato;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private Area obtenerArea(String nombre) throws CajaException
	{
		Area area = null;
		
		switch (nombre) {
			case "Cocina": area = cocina; break;
			case "Barra": area = barra; break;
			case "Cafeteria": area = cafeteria; break;
			default: break;
		}
		if(area == null)
			throw new CajaException("No se ha encontrado el area.");
		
		return area;
	}

	public void eliminarPlato(String nombrePlato) throws PlatoException
	{
		Plato plato = buscarPlato(nombrePlato);
		if(carta.getPlatos().contains(plato))
		{
			try 
			{
				platoSRV.bajaPlato(plato);
				carta.getPlatos().remove(plato);
			} 
			catch (Exception e) 
			{
				throw new PlatoException("Error al grabar el producto.");
			}			
		}
		else
		{
			throw new PlatoException("El producto no existe.");
		}
	}

	public void modifPlato(PlatoVO Plato) throws PlatoException
	{
		Plato plato = new Plato();
		plato.buildFromVO(Plato);
		try 
		{
			platoSRV.modificarPlato(plato);
			Iterator<Plato> platos = carta.getPlatos().iterator();
			//TODO: Verificar que esto funciona correctamente. Con SETs solo se puede recorrer con iterator
			while (platos.hasNext())
			{
				Plato p = platos.next();
				if (p.getId_plato() == plato.getId_plato())
				{
					carta.getPlatos().remove(p);
					break;
				}
			}
			carta.getPlatos().add(plato);
		} 
		catch (Exception e) 
		{
			throw new PlatoException("Error al grabar el producto.");
		}			
	}

	public PlatoVO buscarPlatoVO(String nombre) throws PlatoException
	{
		return platoSRV.buscarPlato(nombre).getVO();
	}

	private Plato buscarPlato(String nombre) throws PlatoException
	{
		return platoSRV.buscarPlato(nombre);
	}
	
	/**
	 * Retorna si el/los depositos tienen la suficiente cantidad
	 * de stock de productos para preparar el plato solicitado.
	 * 
	 * @param platoVO
	 * @return
	 */
	public boolean isPlatoDisponible(String nombrePlato)
	{
		try {
			return platoSRV.isPlatoDisponible(nombrePlato);
		} catch (PlatoException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Genera un pedido.
	 * Se asume que los platos estan todos disponibles.
	 * 
	 * @param nombrePlatos
	 * @param numeroMesa
	 * @param dniMozo
	 * @param isFacturable
	 * 
	 * @return PedidoVO
	 * @throws PedidoException
	 */
	public PedidoVO generarPedido(ArrayList<Integer> idPlatos,
			int numeroMesa, int dniMozo, boolean isFacturable) 
			throws PedidoException
	{
		try 
		{
			Set<Plato> platos = new HashSet <Plato>();
			for(Integer id : idPlatos)
			{
				Plato plato = platoSRV.buscarPlato(id);
				platos.add(plato);
			}
			Mozo mozo = mozoSRV.buscarMozo(dniMozo);
			Mesa mesa = sectorSRV.buscarMesa(numeroMesa, mozo.getSector());
			
			Pedido pedido = pedidosSRV.generarPedido(
					platos, mesa, mozo, isFacturable);
			
			return pedido.getVO();
		} 
		catch (Exception e)
		{
			e.printStackTrace();
			System.out.println(e.getMessage());
			throw new PedidoException("No se a podido generar el pedido.");
		}
	}
	
	/**
	 * Quita un plato pendiente de un area.
	 * 
	 * @param idPlato
	 * @param nombreArea
	 * @throws CajaException 
	 * @throws PlatoException 
	 */
	public void terminarPlato(int idPlato, String nombreArea) 
			throws CajaException, PlatoException
	{
		//Area area = obtenerArea(nombreArea);
		Area area = restaurateSRV.loadArea(nombreArea, this);
		Plato plato = platoSRV.buscarPlato(idPlato);
		platoSRV.terminarPlato(plato, area);
		//TODO: avisar al mozo?? como lo hago?
	}
	
	/**
	 * Apertura de una mesa.
	 * La mesa puede ser combinada.
	 * 
	 * @param dniMozo		dni del mozo a cargo del sector
	 * @param numeroMesa	numero de la mesa a abrir
	 * @param cantPersonas	cantidad de comensales de la mesa
	 * 
	 * @return boolean
	 * @throws MozoException 
	 * @throws MesaException 
	 */
	public void abrirMesa(int numeroMesa, int cantPersonas) 
			throws MozoException, MesaException
	{
		mozoSRV.abrirMesa(numeroMesa, cantPersonas, new Date());
	}
	
	/**
	 * Unir dos o mas mesas creando una MesaCombinada
	 * 
	 * @param dniMozo			dni del mozo a cargo del sector
	 * @param nroMesas 			los numeros de mesa que se quieren combinar
	 * @param maxCantPersonas	maxima cantidad de personas para la mesa
	 * 
	 * @throws MozoException 
	 * @throws MesaException 
	 */
	public void unirMesas(int dniMozo, List<Integer> nroMesas, 
			int maxCantPersonas) 
			throws MozoException, MesaException
	{
		mozoSRV.unirMesas(nroMesas, maxCantPersonas);
	}
	
	/**
	 * Cierre de una mesa.
	 * 
	 * @param numeroMesa
	 * @throws MesaException 
	 * @throws MozoException 
	 */
	public void cerrarMesa(int numeroMesa) 
			throws MozoException, MesaException
	{
		mozoSRV.cerrarMesa(numeroMesa);
	}
	
	/**
	 * Libera la mesa dada.
	 * 
	 * @param numeroMesa
	 * @throws MozoException
	 * @throws MesaException
	 */
	public void liberarMesa(int numeroMesa) 
			throws MozoException, MesaException
	{
		mozoSRV.liberarMesa(numeroMesa);
	}
	
	/**
	 * Genera una factura para una mesa dada con sus pedidos.
	 * 
	 * @param numeroMesa
	 * @param formaPago
	 * @return
	 * @throws CajaException
	 * @throws MesaException
	 */
	public FacturaVO generarFactura(int numeroMesa, String formaPago) 
			throws CajaException, MesaException
	{
		Factura factura = caja.generarFactura(numeroMesa, formaPago);
		cajaSRV.updateCaja(caja);
		return factura.getVO();
	}
	
	public void calcularComisionMozos() throws FacturaException, MozoException {
		this.actualizarCaja();
		List<ComisionMozo>lista= caja.calcularComisionMozo(new Date());
		cajaSRV.cargarComisionMozos(lista);
	}
	
	//TODO: revisar si hace falta este metodo
	private void actualizarCaja() throws FacturaException, MozoException{
		caja.setFacturas(this.facturasrv.cargarAllFacturas());
		caja.setMozos(this.mozoSRV.cargarMozos());
	}
	
	/**
	 * Abre la caja del restaurante, si es posible.
	 * 
	 * @throws CajaException 
	 */
	public void abrirCaja() throws CajaException
	{
		caja.abrirCaja();
		cajaSRV.updateCaja(caja);
	}
	
	/**
	 * Cierre de caja del restaurante
	 * 
	 * @throws CajaException 
	 */	
	public void cerrarCaja(float montoAmano) throws CajaException
	{
		caja.cerrarCaja(montoAmano);
		cajaSRV.updateCaja(caja);
	}
	
	public RestauranteVO getVO() 
	{
		RestauranteVO restVO = new RestauranteVO();
		
		restVO.setIdRastaurante(getIdRastaurante());
		restVO.setNombre(getNombre());
		restVO.setCocina((CocinaVO) getCocina().getVO());
		restVO.setCaja((CajaVO) getCaja().getVO());
		restVO.setSalon((SalonVO) getSalon().getVO());
		restVO.setBarra((BarraVO) getBarra().getVO());
		restVO.setCafeteria((CafeteriaVO)getCafeteria().getVO());
		restVO.setDepositoLocal((DepositoVO) getDepositoLocal().getVO());
		restVO.setCarta((CartaVO) getCarta().getVO());
		
		return restVO;
	}
}

